﻿/*******************************************************************\
| Author:    Mark Esposito mesposit2007@my.fit.edu                  |
| Author:    Andrew Gamez agamez2009@my.fit.edu                     |
| Date:      Nov 16, 2011                                           |
| Class:     SAWGUI                                                 |
| Descr:     The form that acts as a GUI for the user.              |
\*******************************************************************/

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Web;
using System.Windows.Forms.DataVisualization.Charting;
using Microsoft.VisualBasic;
using SaveAWatt.Tests;
using SaveAWatt.EMMObjects;
using DevComponents.DotNetBar;
using ZedGraph;

namespace SaveAWatt.GUI
{
    /**
     * SAWGUI: Save-A-Watt Graphical User Interface.
     */
    public partial class SAWGUI : Office2007Form
    {
        private List<GUIemm> emmList; // Will contain the list of EMMs sent from the SCM.
        private static bool formLoaded = false;
        private ZedGraphControl[] parameterizedZedGraphList;

        /**
         * Constructor for the form.
         */
        public SAWGUI()
        {
            InitializeComponent();
        }

        /**
         * Returns whether the handle has been created.
         */
        public static bool isLoaded()
        {
            return formLoaded;
        }

        /**
         * Assigns a list of EMMs as the list this form uses.
         */
        public void setList(List<GUIemm> x)
        {
            emmList = x;
        }

        /**
         * Gets the form's list of EMMs.
         */
        protected List<GUIemm> getList()
        {
            return emmList;
        }

        /**
         * Insertion sort for an EMM's values by time. Also condenses groups of points into
         * local averages based on graphSharpnessFactor from SettingsForm.
         */
        public GUIemm sortGUIemmByDate(GUIemm original)
        {
            if (original == null)
            {
                return null;
            }
            GUIemm sortedByDate = new GUIemm(original.getID());
            sortedByDate.setName(original.getName());
            int[] ordering = new int[original.getTimes().Count];
            for (int i = 0; i < ordering.Length; i++)
            {
                ordering[i] = i;
            }
            for (int i = 1; i < ordering.Length; i++)
            {
               int j = i;
               double x = timeToDouble(original.getTimes()[i]);
               while ((j > 0) && (timeToDouble(original.getTimes()[ordering[j - 1]]) > x))
               {
                  ordering[j] = ordering[j - 1];
                  j--;
               }
               ordering[j] = i;
            }
            double readingToAdd = 0.0;
            int localAverageCount = 0;
            for (int i = 1; i <= ordering.Length; i++)
            {
                localAverageCount++;
                readingToAdd += original.getReadings()[ordering[i - 1]];
                if (i % (SettingsForm.graphSharpnessFactor / 5) == 0)
                {
                    sortedByDate.setUsage(
                        readingToAdd / localAverageCount,
                        original.getTimes()[ordering[i - 1]]);
                    readingToAdd = 0.0;
                    localAverageCount = 0;
                }
            }
            return sortedByDate;
        }

        /**
         * This method runs whenever the form is resized.
         */
        private void Form1_Resize(object sender, EventArgs e)
        {
            SetSize(zedGraphControl1);
            SetSize(zedGraphControl2);
            SetSize(zedGraphControl3);
        }

        /**
         * Resizes a zedgraph to fit the size of its container.
         */
        private void SetSize(ZedGraphControl zgc)
        {
            zgc.Location = new Point(10, 10);
            zgc.Size = new Size(ClientRectangle.Width - 20, ClientRectangle.Height - 20);
        }

        /**
         * Runs whenever the user clicks an element in the list of EMMs.
         * Note: This also runs when the currently selected element is selected again.
         */
        private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
        {
            // Used to update the labels on the Statistics tab with information from the EMM object x.
            GUIemm x = (GUIemm)listBox1.SelectedItem;
            efficientDisplayUpdate(x);
        }

        /**
         * Assigns default text to labels on the Statistics tab.
         */
        private void defaultStatisticsLabels()
        {
            updateAllEMMStatistics();
            label1.Text = "Save-A-Watt";
            label7.Text = "ID Number";
            label8.Text = "Usage Per Hour";
            label5.Text = "Lifetime Average";
            label9.Text = "Current Cost";
            label10.Text = "Average Cost";
        }

        /**
         * Assigns EMM data to labels on the Statistics tab.
         */
        private void updateStatistics(GUIemm x)
        {
            updateAllEMMStatistics();
            if (x == null)
            {
                defaultStatisticsLabels();
                return;
            }
            label1.Text = x.ToString();
            label1.TextAlign = ContentAlignment.TopCenter;
            label7.Text = x.getID();
            label8.Text = decimalFormat2(x.getUsage());
            label5.Text = decimalFormat2(x.getAverage());
            label9.Text = "$" + decimalFormat2(x.getUsage() * (double) SettingsForm.costPer_kWh);
            label10.Text = "$" + decimalFormat2(x.getAverage() * (double) SettingsForm.costPer_kWh);
        }

        /**
         * Updates the panel in the Statistics tab with general information about all EMMs.
         */
        private void updateAllEMMStatistics()
        {
            int emmListCount = emmList.Count;
            if (emmListCount == 0)
            {
                panel1.Visible = false;
                return;
            }
            label13.Text = emmListCount.ToString();
            double totalUsage = 0.0;
            double totalAverage = 0.0;
            for (int i = 0; i < emmListCount; i++)
            {
                totalUsage += emmList[i].getUsage();
                totalAverage += emmList[i].getAverage();
            }
            totalAverage /= emmListCount;

            label22.Text = decimalFormat2(totalUsage);
            label20.Text = "$" + decimalFormat2(totalUsage * (double) SettingsForm.costPer_kWh);
            label14.Text = decimalFormat2(totalAverage);
            label18.Text = "$" + decimalFormat2(totalAverage * (double) SettingsForm.costPer_kWh);

            panel1.Visible = true;
        }

        /**
         * A way to round a double to be used in a label string.
         */
        private static string decimalFormat2(double x)
        {
            return String.Format("{0:0.00}", (Math.Ceiling(x * 100) / 100));
        }

        /**
         * This method runs one time, immediately after the constructor finishes.
         */
        private void Form1_Load(object sender, EventArgs e)
        {
            emmList = new List<GUIemm>();
            defaultStatisticsLabels();
            defaultGraphLabels();
            parameterizedZedGraphList = new ZedGraphControl[3];
            parameterizedZedGraphList[0] = zedGraphControl1;
            parameterizedZedGraphList[1] = zedGraphControl2;
            parameterizedZedGraphList[2] = zedGraphControl3;
            comboBox1.SelectedIndex = 0;
            formLoaded = true;
            System.Console.WriteLine("[MainGUI] Finished loading.");
        }

        private void defaultGraphLabels()
        {
            zedGraphControl1.GraphPane.XAxis.Title.Text = "Date and Time";
            zedGraphControl1.GraphPane.YAxis.Title.Text = SettingsForm.yAxisString;
            zedGraphControl1.GraphPane.Title.Text = "Single Device Usage History";
            zedGraphControl1.GraphPane.Legend.IsVisible = false;
            zedGraphControl2.GraphPane.XAxis.Title.Text = "Date and Time";
            zedGraphControl2.GraphPane.YAxis.Title.Text = SettingsForm.yAxisString;
            zedGraphControl2.GraphPane.Title.Text = "Relative Usage History";
            zedGraphControl2.GraphPane.Legend.IsVisible = false;
            zedGraphControl3.GraphPane.XAxis.Title.Text = "Proportional Average Usage";
            zedGraphControl3.GraphPane.Legend.IsVisible = false;
        }

        /**
         * A way to convert a DateTime to a double.
         */
        public static double timeToDouble(DateTime x)
        {
            return (double)new XDate(x.ToOADate());
        }

        /**
         * Clears curves and object data from a ZedGraphControl GraphPane.
         */
        private void clearZedGraph(GraphPane x)
        {
            x.CurveList.Clear();
            x.GraphObjList.Clear();
        }

        /**
         * Returns reading times costPer_kWh if the appropriate preference is selected.
         * Otherwise, returns reading.
         */
        private double valueToPlot(double reading)
        {
            if (SettingsForm.useCostForGraphing)
            {
                return reading * (double) SettingsForm.costPer_kWh;
            }
            else
            {
                return reading;
            }
        }

        /**
         * Uses SettingsForm.zoomSettingKey to decide an appropriate date range.
         */
        private DateTime[] findDateRange()
        {
            return findDateRange(SettingsForm.zoomSettingKey);
        }

        /**
         * Returns two DateTime objects that span a range depending on the input.
         * 1: Spans the current day.
         * 1: Spans the past week.
         * 3: Spans the current month.
         * 4: Spans the current year.
         */
        private DateTime[] findDateRange(int zoomSettingKey)
        {

            DateTime currentTime = DateTime.Now;
            DateTime[] range = new DateTime[2];
            if (zoomSettingKey == 1)
            {
                range[0] = new DateTime(currentTime.Year, currentTime.Month, currentTime.Day);
                range[1] = range[0].AddDays(1);
                return range;
            }
            else if (zoomSettingKey == 2)
            {
                DateTime weekStart = new DateTime(currentTime.Year, currentTime.Month, currentTime.Day);
                range[0] = weekStart.AddDays(-6);
                range[1] = range[0].AddDays(7);
                return range;
            }
            else if (zoomSettingKey == 3)
            {
                range[0] = new DateTime(currentTime.Year, currentTime.Month, 1);
                range[1] = range[0].AddMonths(1);
                return range;
            }
            else if (zoomSettingKey == 4)
            {
                range[0] = new DateTime(currentTime.Year, 1, 1);
                range[1] = range[0].AddYears(1);
                return range;
            }
            else
            {
                return null;
            }
        }

        /**
         * Updates the graph for the history of a single EMM.
         */
        private void updateZed1(GUIemm x)
        {
            GUIemm y = sortGUIemmByDate(x);
            GraphPane gPane = zedGraphControl1.GraphPane;
            clearZedGraph(gPane);

            if (y == null || y.getReadings().Count == 0)
            {
                zedGraphControl1.Invalidate();
                pictureBox1.BringToFront();
                return;
            }
            pictureBox1.SendToBack();

            int readingCount = y.getReadings().Count;

            String titleText = "Single Device Usage History";
            String xName = y.ToString();

            PointPairList pointList = new PointPairList();
            if (SettingsForm.useCostForGraphing)
            {
                gPane.YAxis.Scale.Format = "$#0.00";
            }
            else
            {
                gPane.YAxis.Scale.Format = "#";
            }
            gPane.XAxis.Type = ZedGraph.AxisType.Date;
            gPane.XAxis.Scale.Format = "MMMM d\nh:mm tt";
            double minTime = -1.0;
            double maxTime = -1.0;
            for (int i = 0; i < readingCount; i++)
            {
                double currentTime = timeToDouble(y.getTimes()[i]);
                if (currentTime < minTime || minTime < 0)
                {
                    minTime = currentTime;
                }
                if (currentTime > maxTime || maxTime < 0)
                {
                    maxTime = currentTime;
                }
                // Add object's data points to a list.
                pointList.Add(currentTime, valueToPlot(y.getReadings()[i]));
            }

            DateTime[] zoomRange = findDateRange();
            if (zoomRange == null)
            {
                gPane.XAxis.Scale.Min = minTime;
                gPane.XAxis.Scale.Max = maxTime;
            }
            else
            {
                gPane.XAxis.Scale.Min = timeToDouble(zoomRange[0]);
                gPane.XAxis.Scale.Max = timeToDouble(zoomRange[1]);
            }

            if (!xName.Equals(""))
            {
                titleText += (": " + xName);
                // Draw curve based on the list.
                LineItem mainCurve = gPane.AddCurve(xName, pointList, Color.LimeGreen, SymbolType.Circle);
                mainCurve.Line.Width = 2.5F;
            }

            if (SettingsForm.showBlueAverageBar)
            {
                PointPairList averageLine = new PointPairList();
                averageLine.Add(minTime, valueToPlot(y.getAverage()));
                averageLine.Add(maxTime, valueToPlot(y.getAverage()));
                LineItem averageCurve = gPane.AddCurve("Average", averageLine, Color.Blue, SymbolType.Square);
                averageCurve.Line.Width = 2;
            }

            gPane.Title.Text = titleText;

            zedGraphControl1.AxisChange();
            zedGraphControl1.Invalidate();
        }

        /**
         * Updates the graph for history relative to all EMMs.
         */
        private void updateZed2()
        {
            if (emmList.Count == 0)
            {
                zedGraphControl2.Invalidate();
                pictureBox2.BringToFront();
                return;
            }
            pictureBox2.SendToBack();

            GraphPane gPane = zedGraphControl2.GraphPane;
            clearZedGraph(gPane);
            String titleText = "Relative Usage History";
            String xName = null;
            if (SettingsForm.useCostForGraphing)
            {
                gPane.YAxis.Scale.Format = "$#0.00";
            }
            else
            {
                gPane.YAxis.Scale.Format = "#";
            }
            gPane.XAxis.Type = ZedGraph.AxisType.Date;
            gPane.XAxis.Scale.Format = "MMMM d\nh:mm tt";

            double minTime = -1.0;
            double maxTime = -1.0;
            double averageSum = 0.0;

            for (int i = 0; i < listBox1.Items.Count; i++)
            {
                GUIemm x = sortGUIemmByDate((GUIemm)listBox1.Items[i]);
                PointPairList list = new PointPairList();
                for (int i2 = 0; i2 < x.getReadings().Count; i2++)
                {
                    double currentTime = timeToDouble(x.getTimes()[i2]);
                    if (currentTime < minTime || minTime < 0)
                    {
                        minTime = currentTime;
                    }
                    if (currentTime > maxTime || maxTime < 0)
                    {
                        maxTime = currentTime;
                    }
                    list.Add(currentTime, valueToPlot(x.getReadings()[i2]));
                }
                Color lineColor = Color.Red;
                float lineWidth = 1.5F;
                if (i == listBox1.SelectedIndex)
                {
                    lineColor = Color.LimeGreen;
                    lineWidth = 2.0F;
                    xName = x.ToString();
                }
                LineItem curve = gPane.AddCurve(x.ToString(), list, lineColor, SymbolType.Circle);
                curve.Line.IsSmooth = true;
                curve.Line.SmoothTension = 0.1F;
                curve.Line.Width = lineWidth;

                averageSum += x.getAverage();
            }

            if (xName != null && !xName.Equals(""))
            {
                titleText += (": " + xName);
            }

            if (SettingsForm.showBlueAverageBar)
            {
                double averageOfAverages = averageSum / emmList.Count;
                PointPairList averageLine = new PointPairList();
                averageLine.Add(minTime, valueToPlot(averageOfAverages));
                averageLine.Add(maxTime, valueToPlot(averageOfAverages));
                LineItem averageCurve = gPane.AddCurve("Average", averageLine, Color.Blue, SymbolType.Square);
                averageCurve.Line.Width = 1.75F;
            }

            DateTime[] zoomRange = findDateRange();
            if (zoomRange == null)
            {
                gPane.XAxis.Scale.Min = minTime;
                gPane.XAxis.Scale.Max = maxTime;
            }
            else
            {
                gPane.XAxis.Scale.Min = timeToDouble(zoomRange[0]);
                gPane.XAxis.Scale.Max = timeToDouble(zoomRange[1]);
            }

            gPane.Title.Text = titleText;

            zedGraphControl2.AxisChange();
            zedGraphControl2.Invalidate();
        }

        /**
         * Updates the pie chart for usage proportional to all EMMs.
         */
        private void updateZed3()
        {
            if (emmList.Count == 0)
            {
                zedGraphControl3.Invalidate();
                pictureBox3.BringToFront();
                return;
            }
            pictureBox3.SendToBack();

            GraphPane gPane = zedGraphControl3.GraphPane;
            clearZedGraph(gPane);
            String titleText = "Proportional Average Usage";
            String xName = null;

            DateTime[] zoomRange = findDateRange(comboBox1.SelectedIndex);

            for (int i = 0; i < listBox1.Items.Count; i++)
            {
                GUIemm x = sortGUIemmByDate((GUIemm)listBox1.Items[i]);
                Color sliceColor = Color.Red;
                if (i == listBox1.SelectedIndex)
                {
                    sliceColor = Color.LimeGreen;
                    xName = x.ToString();
                }
                double sliceSize;
                if (zoomRange == null)
                {
                    sliceSize = x.getAverage();
                }
                else
                {
                    sliceSize = x.calculateAverageInBounds(zoomRange[0], zoomRange[1]);
                }
                ZedGraph.PieItem segment = gPane.AddPieSlice(sliceSize, sliceColor, Color.White, 45, 0, x.ToString());
                segment.LabelDetail.FontSpec.FontColor = Color.Green;
                segment.LabelType = PieLabelType.Name_Percent;
            }

            if (xName != null && !xName.Equals(""))
            {
                titleText += (": " + xName);
            }
            gPane.Title.Text = titleText;

            zedGraphControl3.AxisChange();
            zedGraphControl3.Invalidate();
        }

        /**
         * Clears the listbox and adds all EMMs from emmList to it.
         */
        public void intializeEmmList()
        {
            listBox1.Items.Clear();
            for (int i = 0; i < emmList.Count(); i++)
            {
                // Add object to listbox.
                GUIemm x = emmList[i];
                listBox1.Items.Add(x);
            }
            GUIemm y = (GUIemm)listBox1.SelectedItem;
            int numberOfReadings = findTotalReadingCount();
            int basePointReduction = 10;
            if (numberOfReadings < basePointReduction * emmList.Count)
            {
                SettingsForm.graphSharpnessFactor = 5;
            }
            else
            {
                SettingsForm.graphSharpnessFactor =
                    (numberOfReadings / (basePointReduction * emmList.Count)) + (numberOfReadings % 5);
            }
            efficientDisplayUpdate(y);
            System.Console.WriteLine("[MainGUI] List initialized.");
        }

        private int findTotalReadingCount()
        {
            int count = 0;
            for (int i = 0; i < emmList.Count; i++)
            {
                count += emmList[i].getReadings().Count;
            }
            return count;
        }

        private void button2_Click(object sender, EventArgs e)
        {
            // Click "Remove"
            int index = listBox1.SelectedIndex;
            if (index != -1)
            {
                string name = listBox1.GetItemText(listBox1.SelectedItem);
                GUIemm x = (GUIemm) listBox1.Items[index];

                DialogResult decision = MessageBox.Show("Warning: Removing this device will remove its history permanently.\n\n" +
                    "Remove: " + name + "?", "Confirm Remove", MessageBoxButtons.YesNo);
                if (decision == DialogResult.Yes)
                {
                    // Perform this action if an item is selected in the ListBox and if the user clicks "OK".
                    emmList.Remove(x);
                    listBox1.Items.RemoveAt(index);
                    System.Console.WriteLine("[MainGUI] Removed " + x.ToString() + " history from GUI.");
                }
            }
        }

        private void button3_Click(object sender, EventArgs e)
        {
            // Click "Rename"
            int index = listBox1.SelectedIndex;
            if (index != -1)
            {
                GUIemm slatedItem = (GUIemm)listBox1.SelectedItem;
                string name = listBox1.GetItemText(slatedItem);
                string newName = Interaction.InputBox("Enter a new name for the outlet:", "Change Name", name, -1, -1);
                while (newName.CompareTo(name) != 0 && !freeName(newName, listBox1))
                {
                    newName = Interaction.InputBox("Name is already taken. Choose a different name:", "Change Name", newName, -1, -1);
                }
                if (newName.CompareTo(name) != 0)
                {
                    // Fake editing. All other ways require too much effort.
                    slatedItem.setName(newName);
                    listBox1.Items.RemoveAt(index);
                    // Note: SelectedIndex becomes -1 here.
                    listBox1.Items.Insert(index, slatedItem);
                    listBox1.SelectedIndex = index;
                    System.Console.WriteLine("[MainGUI] Renamed \"" + name + "\" to \"" + slatedItem.ToString() + "\".");
                }
            }
        }

        /**
         * Returns whether no other EMM in a list has the same name as this one or not.
         * Note: This method might not be necessary.
         */
        private bool freeName(string x, ListBox list)
        {
            ListBox.ObjectCollection s = list.Items;
            foreach (object o in list.Items)
            {
                if (list.GetItemText(o).CompareTo(x) == 0)
                {
                    return false;
                }
            }
            return true;
        }

        /**
         * Only updates the display that the user can see.
         */
        private void efficientDisplayUpdate(GUIemm x)
        {
            if (tabControl1.SelectedIndex == 0)
            {
                if (x == null)
                {
                    defaultStatisticsLabels();
                }
                else
                {
                    updateStatistics(x);
                }
            }
            else if (tabControl1.SelectedIndex == 1)
            {
                updateZed1(x);
            }
            else if (tabControl1.SelectedIndex == 2)
            {
                updateZed2();
            }
            else if (tabControl1.SelectedIndex == 3)
            {
                updateZed3();
            }
        }

        /**
         * Click Deselect. Changes the list box's selected index to -1 and updates zedgraphs.
         */
        private void button4_Click(object sender, EventArgs e)
        {
            listBox1.SelectedIndex = -1;
            efficientDisplayUpdate(null);
            listBox1.Focus();
        }

        private void quitToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Application.Exit();
        }

        private void graphPreferencesToolStripMenuItem_Click(object sender, EventArgs e)
        {
            SettingsForm settings = new SettingsForm();
            settings.ShowDialog(); // Blocking modal dialog here.
            defaultGraphLabels();
            GUIemm x = (GUIemm)listBox1.SelectedItem;
            efficientDisplayUpdate(x);
        }

        private void tabControl1_SelectedIndexChanged(object sender, EventArgs e)
        {
            listBox1.Focus();
            GUIemm x = (GUIemm)listBox1.SelectedItem;
            efficientDisplayUpdate(x);
        }

        /**
         * Changes the zoom of one of the ZedGraphs.
         */
        private void changeGraphZoom(int graphToUpdate, int zoomKeySetting)
        {
            DateTime[] zoomRange = findDateRange(zoomKeySetting);
            if (zoomRange == null)
            {
                efficientDisplayUpdate((GUIemm) listBox1.SelectedItem);
                return;
            }
            parameterizedZedGraphList[graphToUpdate].GraphPane.XAxis.Scale.Min = timeToDouble(zoomRange[0]);
            parameterizedZedGraphList[graphToUpdate].GraphPane.XAxis.Scale.Max = timeToDouble(zoomRange[1]);
            parameterizedZedGraphList[graphToUpdate].AxisChange();
            parameterizedZedGraphList[graphToUpdate].Invalidate();
        }

        private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
        {
            efficientDisplayUpdate((GUIemm) listBox1.SelectedItem);
        }

        private void button12_Click(object sender, EventArgs e)
        {
            changeGraphZoom(0, 0);
        }

        private void button5_Click(object sender, EventArgs e)
        {
            changeGraphZoom(0, 1);
        }

        private void button7_Click(object sender, EventArgs e)
        {
            changeGraphZoom(0, 2);
        }

        private void button6_Click(object sender, EventArgs e)
        {
            changeGraphZoom(0, 3);
        }

        private void button1_Click(object sender, EventArgs e)
        {
            changeGraphZoom(0, 4);
        }

        private void button13_Click(object sender, EventArgs e)
        {
            changeGraphZoom(1, 0);
        }

        private void button11_Click(object sender, EventArgs e)
        {
            changeGraphZoom(1, 1);
        }

        private void button8_Click(object sender, EventArgs e)
        {
            changeGraphZoom(1, 2);
        }

        private void button10_Click(object sender, EventArgs e)
        {
            changeGraphZoom(1, 3);
        }

        private void button9_Click(object sender, EventArgs e)
        {
            changeGraphZoom(1, 4);
        }
    }
}
